#import "CameraController.h"
#import "NDCInfo.h"
#import <misckit/MiscString.h>
#import <video/NXLiveVideoView.h>

static BOOL _NDIsGrabbing;

void handler( DPSTimedEntry teNumber, double now, void *userData )
{
	id grabber;
	
	if( _NDIsGrabbing ) return;
	
	_NDIsGrabbing = YES;
	grabber = (id)userData;
	[grabber grab:nil];
}

@implementation CameraController

- init
{
	id	ourServer;
	NXSize destSize = { 300, 225};
	
	[super init];
	
	_destRect.origin.x = 0;
	_destRect.origin.y = 0;

	_outputRect.origin.x = 0;
	_outputRect.origin.y = 0;
	_outputRect.size.width = 300;
	_outputRect.size.height = 225;
	// [outputView getFrame:&_outputRect];

	currentImage = [NXImage new];
	[currentImage setSize:&destSize];

	outputImage = [NXImage new];	
	// [outputImage setSize:&(outRect.size)];
	[outputImage setSize:&destSize];

	// To allow outside remote grab-control ...puuh cool word :-)
	// we install a plain DO entry.

	ourServer = [NXConnection registerRoot:self 
							 withName:"localhost/NDCamera"];
	[ourServer runFromAppKitWithPriority:NX_MODALRESPTHRESHOLD];

	return self;
}

- showInfo:sender
{
	if( !info ) info = [NDCInfo new];

	[info makeKeyAndOrderFront:self];
	return self;
}

- start:sender
{
	int	i;
	id	aWindow;
	_cameraOn = YES;

	// Slide the button to open..
	// I don't think we have to take care of timed animation because this
	// app will always run on a NeXT Dimension which has fixed speed
	// ...perhaps the Turbos a a little faster...who cares anyway.

	aWindow = [onOffButton window];

	for( i=0; i<7; i++ )
	{
		[onOffButton moveBy:0 :3];
		[aWindow display];
	}
	[self adjustGrabRect:self];	
	[self _adjustOcularTitle];

	[self adjustCameraSource:self];
	[recButton setEnabled:YES];
	[camera start:self];
	[onOffButton setAction:@selector(stop:)];
	[recButton setAction:@selector(startGrabbing:)];
	
	// Grab an initial image...I know that grab will increase the count
	// so lets start with something ueful.

	_frameCount = -1;
	[self grab:self];

	return self;
}

- stop:sender
{
	int	i;
	id	aWindow;

	_cameraOn = NO;
	[recButton setEnabled:NO];
	[self stopGrabbing:self];
	[camera stop:self];

	// Slide the button to close..

	aWindow = [onOffButton window];
	for( i=0; i<7; i++ )
	{
		[onOffButton moveBy:0 :-3];
		[aWindow display];
	}
	[self adjustGrabRect:self];	
	[onOffButton setAction:@selector(start:)];
	return self;
}

- grab:sender
{
	_NDIsGrabbing = YES;
	[camera grabIn:currentImage 
				fromRect:&_sourceRect 
				toRect:&_destRect];

	_frameCount++;
	[frameNumber setIntValue:_frameCount];

	[currentImage setScalable:YES];
	[outputImage lockFocus];

	// One possible way of getting the image where we want it:
	//
	//	[currentImage setSize:&(_outputRect.size)];
	//	[currentImage composite:NX_COPY toPoint:&aPoint];
	//	[currentImage setSize:&(_destRect.size)];

	// Our way to do it

	[currentImage drawRepresentation:[currentImage bestRepresentation]
				inRect:&_outputRect];

	// another way....
	//
	//	aImageRep = [currentImage bestRepresentation];
	//	[aImageRep drawIn:&_outputRect];
	
	[outputImage unlockFocus];

	[outputView setImage:outputImage];
	NXPing();

	// Lets see if we have to save that stuff ??

	if( grabOutput != nil )[self _saveCurrentFrame];

	_NDIsGrabbing = NO;

	return self;
}

- _saveCurrentFrame
{
	id	aString;
	id	filename;
	FILE	 * 		dummyStream;
	NXStream * 	aStream;
	float	compressionFactor;

	filename = [grabOutput copy];
	aString = [filename fileBasename];
	[filename cat:"/"];
	[filename concatenate:aString];
	[filename cat:"."];
	[aString setIntValue:_frameCount];
	[filename concatenate:aString];
	[filename cat:".tiff"];
	NXLogError( [filename stringValue] );

	// Well the streams won't truncate a file so we have to do it on
	// our own.
	// We should do a backup of existing files here if requested.

	dummyStream = fopen( [filename stringValue], "w" );
	if( dummyStream ) fclose( dummyStream );
		
	aStream = NXMapFile( [filename stringValue], NX_WRITEONLY );

	[outputImage writeTIFF:aStream 
		allRepresentations:NO
//		usingCompression:NX_TIFF_COMPRESSION_LZW
		usingCompression:NX_TIFF_COMPRESSION_NONE
		andFactor:0];

	NXSaveToFile( aStream, [filename stringValue] );
	NXCloseMemory( aStream, NX_FREEBUFFER );

	[aString free];
	[filename free];
	return self;
}

- startGrabbing:sender
{
	// Now if we have to do autorecording lets init the timed entry...

	if( [autoGrabButton state] == YES )
	{
		[self _startTimer];
		[recButton setAction:@selector(stopGrabbing:)];
		[recButton setState:1];
	}
	// otherwise we will just grab a single frame.

	else
	{
		[recButton setState:1];
		[self grab:self];
		[recButton setState:0];
	}
	return self;
}

- adjustOutputFile:pathString
{
	_frameCount = 0;
	[frameNumber setIntValue:_frameCount];

	if( !grabOutput )
		grabOutput = [MiscString new];

	[grabOutput takeStringValueFrom:pathString];

	if( ![grabOutput isFileOfType:Misc_Directory] ||
		[grabOutput isEmpty] )
	{
		[grabOutput free];
		grabOutput = nil;
		[grabOutputField setStringValue:""];
	}
	else
	{
		[grabOutputField setStringValue:[grabOutput stringValue]];
	}
	return self;
}

- stopGrabbing:sender
{
	[self _stopTimer];
	[recButton setAction:@selector(startGrabbing:)];
	[recButton setState:0];
	return self;
}

- (BOOL)isGrabbing
{
	return (BOOL)[recButton state];
}

- adjustSpeed:sender
{
	if( !_cameraOn ) return self;
	[self stopGrabbing:self];
	[self startGrabbing:self];
	return self;
}

- _startTimer
{
	double	frameDelay;
	float	aBaseRate;

	if( animateTE == 0 )
	{
		aBaseRate = [fpsBaseRateField floatValue];
		if( aBaseRate < 0.001 ) aBaseRate = 0.001;

		frameDelay = 1 / ([[speedMatrix selectedCell] tag] * aBaseRate);
		animateTE = DPSAddTimedEntry( frameDelay, (DPSTimedEntryProc)handler,
									  self, NX_BASETHRESHOLD );
	}
	return self;
}

- _stopTimer
{
	if( animateTE )
	{	
		DPSRemoveTimedEntry( animateTE );
		animateTE = 0;
	}
	return self;
}

- baseFpsRateChanged:sender
{

	return self;
}

- adjustCameraSource:sender
{
	[camera selectInput:[[inputMatrix selectedCell] tag]];
	return self;
}

- adjustGrabRect:sender
{
	[self _adjustGrabRect];
	
	_refFrameSize.width = _sourceRect.size.width;
	_refFrameSize.height = _sourceRect.size.height;

	return self;
}

- _adjustGrabRect
{
	// BUG: We really should make some sanity checks !!

	_sourceRect.origin.x = [grabOriginXField intValue];
	_sourceRect.origin.y = [grabOriginYField intValue];
	_sourceRect.size.width = [grabSizeWidthField intValue];
	_sourceRect.size.height = [grabSizeHeightField intValue];	
	// Lets get a new image with the right size...Remember that this size
	// does not reflex the size of the saved image !! This has to be resized
	// to reflect the different scale.

	_destRect.size.width = _sourceRect.size.width;
	_destRect.size.height = _sourceRect.size.height;

	if( currentImage ) [currentImage free];
	currentImage = [NXImage new];
	[currentImage setSize:&(_destRect.size)];

	return self;
}

- zoomIn:sender
{
	float	xInc;
	float	yInc;

	// Be sure that there will be still a useful rect to see.

	xInc = _refFrameSize.width / 100;
	if( xInc < 2 ) xInc = 2;

	yInc = _refFrameSize.height / 100;
	if( yInc < 2 ) yInc = 2;

	if( _sourceRect.size.width > 20 &&
		_sourceRect.size.height > 20 )
	{
		[grabOriginXField setIntValue:_sourceRect.origin.x + xInc];
		[grabOriginYField setIntValue:_sourceRect.origin.y + yInc];
		[grabSizeWidthField setIntValue:_sourceRect.size.width - 2* xInc];
		[grabSizeHeightField setIntValue:_sourceRect.size.height - 2* yInc];
	}
	[self _adjustGrabRect];	
	[self grab:self];
	return self;
}

- zoomOut:sender
{
	float	xInc;
	float	yInc;

	// lets not pass the lower left corner..

	xInc = _refFrameSize.width / 100;
	if( xInc < 2 ) xInc = 2;

	yInc = _refFrameSize.height / 100;
	if( yInc < 2 ) yInc = 2;

	if( _sourceRect.origin.x > 4 &&
		_sourceRect.origin.y > 4 )
	{
		[grabOriginXField setIntValue:_sourceRect.origin.x - xInc];
		[grabOriginYField setIntValue:_sourceRect.origin.y - yInc];
		[grabSizeWidthField setIntValue:_sourceRect.size.width + 2* xInc];
		[grabSizeHeightField setIntValue:_sourceRect.size.height + 2* yInc];
	}
	[self _adjustGrabRect];	
	[self grab:self];
	return self;
}

- openVideoControls:sender
{
	int	i;
	int	stepSize;
	int	direction;
	id	aWindow;
	NXRect	aFrame;

	// opens and closes the controls area...the name is not very cleaver..
	// but who cares.

	[sender getFrame:&aFrame];

	if( aFrame.origin.x < 100 )
			direction = +1;
	else	direction = -1;

	stepSize = 6 * direction;

	aWindow = [sender window];
	for( i=0; i<14; i++ )
	{
		[sender moveBy:stepSize :0];
		[aWindow display];
	}

	stepSize = 3 * direction;

	for( i=0; i<3; i++ )
	{
		[sender moveBy:stepSize :0];
		[aWindow display];
	}

	return self;
}

- changeHue:sender
{
	[camera setInputHue:
		[camera inputHue] + ([sender tag] * 36.0)];
	[sender setState:0];
	
	// We lack any visual feedback for the single values...so we'll use the
	// console...sorry...thats nasty; I know.

	NXLogError( "Hue: %f", [camera inputHue] );
	return self;
}

- changeBrightness:sender
{
	[camera setInputBrightness:
		[camera inputBrightness] + ([sender tag] / 10.0)];
	[sender setState:0];

	// We lack any visual feedback for the single values...so we'll use the
	// console...sorry...thats nasty; I know.

	NXLogError( "Brightness: %f", [camera inputBrightness] );
	return self;
}

- changeSaturation:sender
{
	[camera setInputSaturation:
		[camera inputSaturation] + ([sender tag] / 10.0)];
	[sender setState:0];

	// We lack any visual feedback for the single values...so we'll use the
	// console...sorry...thats nasty; I know.

	NXLogError( "Saturation: %f", [camera inputSaturation] );
	return self;
}

- changeGamma:sender
{
	[camera setInputGamma:
		[camera inputGamma] + ([sender tag] / 10.0)];
	[sender setState:0];

	// We lack any visual feedback for the single values...so we'll use the
	// console...sorry...thats nasty; I know.

	NXLogError( "Gamma: %f", [camera inputGamma] );
	return self;
}

- changeSharpness:sender
{
	[camera setInputSharpness:
		[camera inputSharpness] + ([sender tag] / 10.0)];
	[sender setState:0];

	// We lack any visual feedback for the single values...so we'll use the
	// console...sorry...thats nasty; I know.

	NXLogError( "Sharpness: %f", [camera inputSharpness] );
	return self;
}

- windowDidMove:sender
{
	// If the window should stick together...we'll do it.

	// if( !keepWindowsTogether ) return self;

	return self;
}

- windowDidResize:sender
{
	NXRect	aFrame;

	// If our ocular has changed we should adjust the title and the 
	// image grabbing area.

	if( sender != [outputView window] ) return self;

	[outputView getBounds:&aFrame];
	[outputImage setSize:&(aFrame.size)];
	_outputRect.size.width = aFrame.size.width;
	_outputRect.size.height = aFrame.size.height;

	[self _adjustOcularTitle];

	// We should grab the image again...but it would be faster to just scale
	// the old one...

	// [self grab:self];

	return self;
}

- _adjustOcularTitle
{
	id	ourWindow;
	id	aString;
	id	intString;

	ourWindow = [outputView window];
	aString = [MiscString newWithString:"("];
	intString = [MiscString new];
	[intString setIntValue:_outputRect.size.width];
	[aString concatenate:intString];
	[aString cat:" x "];
	[intString setIntValue:_outputRect.size.height];
	[aString concatenate:intString];
	[aString cat:") Ocular"];

	[ourWindow setTitle:[aString stringValue]];

	[aString free];
	[intString free];	

	return self;
}

@end